﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace ApiDoc.CodeSyntaxGenerators.Languages
{
    /// <summary>
    /// C# language generator for the Api Documentation.
    /// </summary>
    [CodeSyntaxGenerator("C#")]
    public class CSharpCodeSyntaxGenerator : CodeSyntaxGeneratorBase
    {
        public override string GenerateName(Type type)
        {
            var ret = new StringBuilder();

            if (type.IsGenericTypeDefinition) {
                int index = type.Name.IndexOf('`');
                if (index >= 0) {
                    ret.AppendFormat("{0}<", type.Name.Substring(0, index));
                } else {
                    ret.AppendFormat("{0}<", type.Name);
                }
                ret.Append(GenerateGenericArgumentList(type.GetGenericArguments()));
                ret.Append(">");
            } else if (type.IsGenericType) {
                int index = type.Name.IndexOf('`');
                if (index >= 0) {
                    ret.AppendFormat("{0}<", type.Name.Substring(0, index));
                } else {
                    ret.AppendFormat("{0}<", type.Name);
                }
                var arguments = type.GetGenericArguments();
                for (int i = 0; i < arguments.Count(); ++i) {
                    ret.Append(GenerateName(arguments[i]));

                    if (i != arguments.Count() - 1) {
                        ret.Append(", ");
                    }
                }
                ret.Append(">");
            } else {
                ret.Append(type.Name);
            }

            return ret.ToString();
        }

        public override string GenerateName(System.Reflection.MethodInfo method)
        {
            var ret = new StringBuilder();

            ret.Append(method.Name);

            if (method.ContainsGenericParameters) {
                ret.Append("<");
                ret.Append(GenerateGenericArgumentList(method.GetGenericArguments()));
                ret.Append(">");
            }

            // Add parameters
            ret.Append("(");
            if (method.IsDefined(typeof(ExtensionAttribute), false)) {
                ret.Append("this ");
            }
            ret.Append(GenerateParameterList(method.GetParameters(), includeNames: false));
            ret.Append(")");

            return ret.ToString(); 
        }

        public override string GenerateName(System.Reflection.PropertyInfo property)
        {
            var ret = new StringBuilder();

            ret.Append(property.Name);

            return ret.ToString(); 
        }

        public override string GenerateName(System.Reflection.EventInfo eventx)
        {
            var ret = new StringBuilder();

            ret.Append(eventx.Name);

            return ret.ToString();
        }

        public override string GenerateName(System.Reflection.ConstructorInfo constructor)
        {
            var ret = new StringBuilder();

            if (constructor.ReflectedType.IsGenericType) {
                ret.Append(constructor.ReflectedType.Name.Substring(0, constructor.ReflectedType.Name.IndexOf('`')));
            } else {
                ret.Append(constructor.ReflectedType.Name);
            }

            //if (constructor.ContainsGenericParameters) {
            //    ret.Append("<");
            //    ret.Append(GenerateGenericArgumentList(constructor.GetGenericArguments()));
            //    ret.Append(">");
            //}

            // Add parameters
            ret.Append("(");
            ret.Append(GenerateParameterList(constructor.GetParameters(), includeNames: false));
            ret.Append(")");

            return ret.ToString();
        }

        public override string GenerateName(System.Reflection.FieldInfo field)
        {
            var ret = new StringBuilder();

            ret.Append(field.Name);

            return ret.ToString(); 
        }

        public override string GenerateName(System.Reflection.ParameterInfo parameter)
        {
            var ret = new StringBuilder();

            ret.Append(parameter.Name);

            return ret.ToString();
        }

        public override string GenerateCodeDefinition(Type type)
        {
            var ret = new StringBuilder();

            //// Attributes
            //foreach (System.Attribute attribute in m_element.GetCustomAttributes(false)) {
            //    // TODO
            //}

            // Scope modifier.
            if ((type.Attributes & TypeAttributes.Public) == TypeAttributes.Public) {
                ret.Append("public ");
            } else if ((type.Attributes & TypeAttributes.NestedAssembly) == TypeAttributes.NestedAssembly) {
                ret.Append("internal ");
            } else if (
                (type.Attributes & TypeAttributes.NestedFamANDAssem) == TypeAttributes.NestedFamANDAssem
                || (type.Attributes & TypeAttributes.NestedFamORAssem) == TypeAttributes.NestedFamORAssem
                ) {
                ret.Append("protected internal ");
            } else if ((type.Attributes & TypeAttributes.NestedPrivate) == TypeAttributes.NestedPrivate) {
                ret.Append("private ");
            } else if ((type.Attributes & TypeAttributes.NestedPublic) == TypeAttributes.NestedPublic) {
                ret.Append("public ");
            }

            // Other attributes
            if (
                (type.Attributes & TypeAttributes.Abstract) == TypeAttributes.Abstract
                && !type.IsInterface
                ) {
                ret.Append("abstract ");
            }

            if ((type.Attributes & TypeAttributes.Sealed) == TypeAttributes.Sealed) {
                ret.Append("sealed ");
            }

            // TODO handle static classes

            // Type of Type
            if (type.IsClass) {
                ret.Append("class ");
                //} else if (m_element.IsDelegate) {
                //    ret.Append("delegate ");
            } else if (type.IsEnum) {
                ret.Append("enum ");
            } else if (type.IsInterface) {
                ret.Append("interface ");
            }

            // Class name in friendly form
            ret.Append(GenerateName(type));

            // Base class
            bool needComma = false;
            if (type.BaseType != null) {
                ret.AppendFormat(" : {0}", GenerateName(type.BaseType));
                needComma = true;
            }

            var interfaces = type.GetInterfaces();
            for (int i = 0; i < interfaces.Count(); ++i) {
                if (needComma) {
                    ret.Append(", ");
                } else {
                    ret.Append(" : ");
                }

                ret.Append(GenerateName(interfaces[i]));
            }

            // TODO generic type constraints.

            return ret.ToString();
        }

        public override string GenerateCodeDefinition(System.Reflection.MethodInfo method)
        {
            var ret = new StringBuilder();

            // Attributes
            if ((method.Attributes & MethodAttributes.Public) == MethodAttributes.Public) {
                ret.Append("public ");
            } else if ((method.Attributes & MethodAttributes.Assembly) == MethodAttributes.Assembly) {
                ret.Append("internal ");
            } else if (
                (method.Attributes & MethodAttributes.FamANDAssem) == MethodAttributes.FamANDAssem
                || (method.Attributes & MethodAttributes.FamORAssem) == MethodAttributes.FamORAssem
                ) {
                ret.Append("protected internal ");
            } else if ((method.Attributes & MethodAttributes.Family) == MethodAttributes.Family) {
                ret.Append("protected ");
            }else if (
                (method.Attributes & MethodAttributes.Private) == MethodAttributes.Private
                || (method.Attributes & MethodAttributes.PrivateScope) == MethodAttributes.PrivateScope
                ) {
                ret.Append("private ");
            }

            if ((method.Attributes & MethodAttributes.Static) == MethodAttributes.Static) {
                ret.Append("static ");
            }

            if ((method.Attributes & MethodAttributes.Abstract) == MethodAttributes.Abstract) {
                ret.Append("abstract ");
            } else if ((method.Attributes & MethodAttributes.Virtual) == MethodAttributes.Virtual) {
                var baseDefinition = method.GetBaseDefinition();
                if (baseDefinition != null) {
                    ret.Append("override ");
                } else {
                    ret.Append("virtual ");
                }
            }

            if ((method.Attributes & MethodAttributes.Final) == MethodAttributes.Final) {
                ret.Append("sealed ");
            }

            //if (
            //    (method.Attributes & MethodAttributes.HideBySig) != 0
            //    ) {
            //    ret.Append("new ");
            //}

            

            // Return type.
            if (method.ReturnType == null) {
                ret.Append("void ");
            } else {
                ret.Append(GenerateName(method.ReturnType));
                ret.Append(" ");
            }

            ret.Append(method.Name);

            if (method.ContainsGenericParameters) {
                ret.Append("<");
                ret.Append(GenerateGenericArgumentList(method.GetGenericArguments()));
                ret.Append(">");
            }

            // Add parameters
            ret.Append("(");
            if (method.IsDefined(typeof(ExtensionAttribute), false)) {
                ret.Append("this ");
            }
            ret.Append(GenerateParameterList(method.GetParameters(), includeNames: true));
            ret.Append(")");

            return ret.ToString();
        }

        public override string GenerateCodeDefinition(System.Reflection.PropertyInfo property)
        {
            var ret = new StringBuilder();

            // Return type
            ret.Append(GenerateName(property.PropertyType));
            ret.Append(" ");

            ret.Append(property.Name);

            // Add Get and/or Set
            ret.Append(" {");
            if (property.CanRead) {
                ret.Append(" get; ");
            }
            if (property.CanWrite) {
                ret.Append(" set; ");
            }
            ret.Append("}");

            return ret.ToString();
        }

        public override string GenerateCodeDefinition(System.Reflection.ConstructorInfo constructor)
        {
            var ret = new StringBuilder();

            if (constructor.ReflectedType.IsGenericType) {
                ret.Append(constructor.ReflectedType.Name.Substring(0, constructor.ReflectedType.Name.IndexOf('`')));
            } else {
                ret.Append(constructor.ReflectedType.Name);
            }

            //if (constructor.ContainsGenericParameters) {
            //    ret.Append("<");
            //    ret.Append(GenerateGenericArgumentList(constructor.GetGenericArguments()));
            //    ret.Append(">");
            //}

            // Add parameters
            ret.Append("(");
            ret.Append(GenerateParameterList(constructor.GetParameters(), includeNames: true));
            ret.Append(")");

            return ret.ToString();
        }

        public override string GenerateCodeDefinition(System.Reflection.EventInfo eventx)
        {
            var ret = new StringBuilder();

            ret.Append("event ");

            // Handler type
            ret.Append(GenerateName(eventx.EventHandlerType));
            ret.Append(" ");

            ret.Append(eventx.Name);

            return ret.ToString();
        }

        public override string GenerateCodeDefinition(System.Reflection.FieldInfo field)
        {
            var ret = new StringBuilder();

            // Return type
            ret.Append(GenerateName(field.FieldType));
            ret.Append(" ");

            ret.Append(field.Name);

            return ret.ToString();
        }

        public override string GenerateCodeDefinition(System.Reflection.ParameterInfo parameter)
        {
            return GenerateCodeDefinition(parameter, includeName: true);
        }

        protected string GenerateCodeDefinition(System.Reflection.ParameterInfo parameter, bool includeName)
        {
            var ret = new StringBuilder();

            if ((parameter.Attributes & ParameterAttributes.Out) != 0) {
                ret.Append("out ");
            }

            if ((parameter.Attributes & ParameterAttributes.In) != 0) {
                ret.Append("ref ");
            }

            ret.Append(GenerateName(parameter.ParameterType));

            if (includeName) {
                ret.Append(" ");
                ret.Append(parameter.Name);
            }

            if (parameter.IsOptional) {
                if (parameter.DefaultValue == null) {
                    ret.Append("= null");
                } else if (parameter.ParameterType == typeof(string)) {
                    ret.AppendFormat("= \"{0}\"", parameter.DefaultValue.ToString());
                } else {
                    ret.AppendFormat("= {0}", parameter.DefaultValue.ToString());
                }
            }

            return ret.ToString();
        }
                
        /// <summary>
        /// Generates a generic argument list for a syntax.
        /// </summary>
        /// <param name="parameters"></param>
        /// <returns></returns>
        protected string GenerateGenericArgumentList(Type[] arguments)
        {
            var ret = new StringBuilder();

            for (int i = 0; i < arguments.Count(); ++i) {
                ret.Append(arguments[i].Name);

                if (i != arguments.Count() - 1) {
                    ret.Append(", ");
                }
            }

            return ret.ToString();
        }

        /// <summary>
        /// Generates a parameter list for a syntax.
        /// </summary>
        /// <param name="parameters"></param>
        /// <returns></returns>
        protected string GenerateParameterList(ParameterInfo[] parameters, bool includeNames)
        {
            var ret = new StringBuilder();

            for (int i = 0; i < parameters.Count(); ++i) {
                var parameter = parameters[i];

                ret.Append(GenerateCodeDefinition(parameter, includeName: includeNames));

                if (i != parameters.Count() - 1) {
                    ret.Append(", ");
                }
            }

            return ret.ToString();
        }
    }
}
